using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;

namespace AnimCollections {
    public unsafe struct GraphBuffer : IDisposable {
        [StructLayout(LayoutKind.Sequential)]
        struct Block {
            public Block* prev;
            public Block* next;
            public byte* writePosition; // 此block中下一个被写入的位置
            public byte* blockEnd; // 最后一个字节，这个位置是可用的（包含）
            public fixed byte data[1];
        }

        public int maxDataSizeOfBlock => oneBlockSize - 4 * sizeof(byte*); // 允许一次分配的最大尺寸，也就是一个Block中可容纳的数据大小
        public int allDataSize => maxDataSizeOfBlock * (blockCount - 1) + (int)(tailBlock->writePosition - tailBlock->data); // 所有数据总大小，不包括头

        // 必须是2的幂
        // sizeod(Block) = 32，所以一定要大于32，真正存储空间为 minAllocationSize - 24
        // 要大于16，因为默认16字节对齐
        // 要大于64，因为缓存行一般为64字节
        // 要大于 单次分配的大小 + 8 * 3
        const int minAllocationSize = 1024;
        public int blockCount { get; private set; }

        readonly int oneBlockSize;
        Block* headBlock;

        Block* tailBlock;
        //byte* currentPtr; // 下一个被写入的位置
        public readonly bool IsCreated {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => tailBlock != null;
        }

        /// <summary>
        /// 为了正确构造，必须执行此构造函数，参数已经做了保底，传个0也没事
        /// </summary>
        /// <param name="allocationSize">最少为 minAllocationSize</param>
        public GraphBuffer(int allocationSize) {
            this = default;
            oneBlockSize = GetOneBlockSize(allocationSize);
            var newBlock = NewBlock(ref this);
            //currentPtr = newBlock->data;
        }

        static int GetOneBlockSize(int x) {
            x = x > minAllocationSize ? x : minAllocationSize;
            return (x + 1024 - 1) / 1024 * 1024; // 向上取整为 1024 的倍数
        }

        public void Dispose() {
#if ANIM_DEBUG
            var overBytes = currentPtr - tailBlock->data;
            UnityEngine.Debug.Log($"GraphBuffer.Dispose(): blockCount = {blockCount}; over size = {overBytes}; total memory = {(blockCount - 1) * allocationSize + overBytes}");
#endif
            var curBlock = headBlock;
            while (curBlock != null) {
                var next = curBlock->next;
                Memory.Unmanaged.Array.Resize(curBlock, oneBlockSize, 0, Allocator.Persistent, 1, 8);
                curBlock = next;
            }
            headBlock = null;
            tailBlock = null;
            //currentPtr = null;
        }

        /// <summary>
        /// 获取 block 0 的数据区域起止地址
        /// </summary>
        public (ulong, ulong) GetBlockDataAddrRange() {
            if (headBlock == null) {
                return (0, 0);
            }
            return ((ulong)headBlock->data, (ulong)headBlock->writePosition); // [,)
        }

        public Span<byte> Dump() {
            var dstBuffer = (byte*)UnsafeUtility.Malloc(allDataSize, 8, Allocator.Temp);
            long pos = 0;

            var curBlock = headBlock;
            while (curBlock != null) {
                var size = curBlock->writePosition - curBlock->data;
                UnsafeUtility.MemCpy(dstBuffer + pos, curBlock->data, size);
                pos += size;
                curBlock = curBlock->next;
            }

            UnityEngine.Debug.Assert(pos == allDataSize, $"Dump 尺寸异常 pos = {pos}, allDataSize = {allDataSize}");
            Span<byte> buffer = new Span<byte>(dstBuffer, allDataSize);
            return buffer;
        }

        public T* Malloc<T>() where T : unmanaged {
            // 测试用
            //return (T*)UnsafeUtility.MallocTracked(UnsafeUtility.SizeOf<T>(), UnsafeUtility.AlignOf<T>(), Allocator.Persistent, 0);

            int size = sizeof(T);
            T* obj = (T*)Allocate(size);
            return obj;
        }

        public void MallocBytes(byte[] bytes) {
            var ptr = Allocate(bytes.Length);
            fixed (byte* sourcePtr = bytes) {
                UnsafeUtility.MemCpy(ptr, sourcePtr, bytes.Length);
            }
        }

        //public T* MallocArray<T>(int length) where T : unmanaged {
        //    int size = UnsafeUtility.SizeOf<T>();
        //    T* obj = (T*)Allocate(size * length);
        //    return obj;
        //}

        /// <summary>
        /// 在buffer中构造一个NativeArray
        /// 内容置零
        /// </summary>
        public void MallocNativeArray<T>(out NativeArray<T> outArray, int length, NativeArrayOptions options = NativeArrayOptions.ClearMemory) where T : unmanaged {
            //// 测试用
            //array = new NativeArray<T>(length, Allocator.Persistent);
            //return;

            if (length <= 0) {
                throw new IndexOutOfRangeException();
            }
            int size = sizeof(T) * length;
            var buffer = Allocate(size);

            if ((options & NativeArrayOptions.ClearMemory) == NativeArrayOptions.ClearMemory)
                UnsafeUtility.MemClear(buffer, size);

            outArray.m_Buffer = buffer;
            outArray.m_Length = length;
            outArray.m_AllocatorLabel = Allocator.None;
            // outArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(buffer, length, Allocator.None); // gpt建议用这个取代直接赋值，以后看看
#if ENABLE_UNITY_COLLECTIONS_CHECKS
            outArray.m_MinIndex = 0;
            outArray.m_MaxIndex = length - 1;
            outArray.m_Safety = CollectionHelper.CreateSafetyHandle(Allocator.None);
#endif
        }

        /// <summary>
        /// 在buffer中构造一个NativeArray,并且使用传入的数组初始化
        /// </summary>
        public void MallocNativeArray<T>(out NativeArray<T> outArray, T[] array) where T : unmanaged {
            MallocNativeArray(out outArray, array.Length, NativeArrayOptions.UninitializedMemory);
            NativeArray<T>.Copy(array, outArray);
        }

        public void MallocNativeSimpleArray<T>(out NativeSimpleArray<T> outArray, int length, NativeArrayOptions options = NativeArrayOptions.ClearMemory) where T : unmanaged {
            int size = sizeof(T) * length;
            var buffer = Allocate(size);
            if ((options & NativeArrayOptions.ClearMemory) == NativeArrayOptions.ClearMemory)
                UnsafeUtility.MemClear(buffer, size);
            outArray.m_Buffer = buffer;
            outArray.m_Length = length;
        }

        public void MallocNativeSimpleArray<T>(out NativeSimpleArray<T> outArray, T[] array) where T : unmanaged {
            MallocNativeSimpleArray(out outArray, array.Length, NativeArrayOptions.UninitializedMemory);
            outArray.CopyFrom(array);
        }

        private byte* Allocate(int size) {
            // 思考这里分配需不需要考虑内存对齐，目前是紧凑排列的（划掉，对齐到8了）
            size = (size + 7) & ~7; // 对齐到8
            
            if (size > maxDataSizeOfBlock) {
                throw new OutOfMemoryException($"GraphBuffer: size of Block is too small {size}/{maxDataSizeOfBlock}");
            }

            byte* ptr = tailBlock->writePosition;
            var endPtr = tailBlock->writePosition + size;

            if (endPtr > tailBlock->blockEnd) {
                var newBlock = NewBlock(ref this);
                ptr = newBlock->writePosition;
                newBlock->writePosition += size;
#if ANIM_DEBUG
                UnityEngine.Debug.Log($"GraphBuffer: Allocate {size} size, block count grow to {blockCount}, remaining {tailBlock->blockEnd - currentPtr}");
#endif
            } else {
#if ANIM_DEBUG
                UnityEngine.Debug.Log($"GraphBuffer: Allocate {size} size, remaining {tailBlock->blockEnd - currentPtr}");
#endif
                tailBlock->writePosition = endPtr;
            }

            return ptr;
        }

        static Block* NewBlock(ref GraphBuffer buffer) {
            Block* block = (Block*)Memory.Unmanaged.Array.Resize(null, 0, buffer.oneBlockSize, Allocator.Persistent, 1, 8);
            block->blockEnd = (byte*)block + buffer.oneBlockSize - sizeof(byte*);
            block->writePosition = block->data;

            if (buffer.tailBlock == null) {
                buffer.headBlock = block;
                buffer.tailBlock = block;
                block->next = null;
                block->prev = null;
            } else {
                buffer.tailBlock->next = block;
                block->prev = buffer.tailBlock;
                block->next = null;
                buffer.tailBlock = block;
            }
            ++buffer.blockCount;
            return block;
        }
    }
}
